package jwcb.javapjframework.common.core.utils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import jwcb.javapjframework.common.domain.constants.CommonConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * JSON工具类
 * 提供:
 *  1. 对象转JSON
 *  2. JSON转对象
 */
@Slf4j
public class JsonUtil {

    /**
     * 对象转换器
     */
    private static ObjectMapper OBJECT_MAPPER;

    /**
     * init...
     * 1. .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
     *          在反序列化时，当遇到 JSON 数据中存在 Java 对象对应类中没有定义的属性时，默认情况下
     *          Jackson 会抛出异常。通过将这个配置项设置为 false，表示允许在反序列化时忽略那些未知的属
     *          性，不会因为出现额外的属性而导致反序列化失败，增强了对不同来源 JSON 数据的兼容性。
     *          例如: Student:name, 如果传进来的JSON为Student:name,sex,但是sex并不存在,那么设置为false将忽略sex
     *  <br>
     * 2. .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
     *          在序列化时，如果Java 对象中包含日期类型默认情况下 Jackson 可能会将日期转换为时间戳来表示。
     *          将此配置设置为false，意味着不采用时间戳的方式来表示日期，而是会按照后续配置的其他日期相关格式进行序列化，
     *          以便生成更具可读性的日期格式表示在 JSON 数据中。
     *  <br>
     * 3. .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
     *          在序列化时，Java对象中没有任何属性值（为空），默认情况下 Jackson 可能会抛出异常。设置
     *          此项为 false 后，允许对这样的空对象进行序列化，即便对象没有实质内容也能生成对应的 JSON
     *          表示（通常可能是一个空的 JSON 对象 {}），避免了因对象为空导致序列化失败的情况。
     *  <br>
     * 4. .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)
     *          在反序列化时，如果 JSON 数据中指定的类型信息与期望的 Java 类型层次结构不匹配（例如类型
     *          标识错误等情况），默认会抛出异常。将这个配置设为 false，可以放宽这种限制，使得在遇到类
     *          型不太准确但仍有可能处理的情况下，尝试继续进行反序列化而不是直接失败，提高对可能存在错
     *          误类型标识的 JSON 数据的容错性。
     *  <br>
     * 5. .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false)
     *          在序列化时，如果 Java 对象中有以日期类型作为键（比如在 Map 中，键是 Date 类型等情况）的
     *          结构，如果设置为true会将日期键转换为时间戳形式。设置为 false 则按照其他日期相关配置来处
     *          理，保证日期键也以期望的格式进行序列化，使得整个 JSON 结构中日期相关内容都能保持统一的
     *          格式呈现。(默认值为false)
     *  <br>
     * 6. .configure(MapperFeature.USE_ANNOTATIONS, false)
     *          Jackson 支持通过在 Java 类的属性或方法上添加各种注解来定制序列化和反序列化行为。将此配
     *          置设为 false，表示不依赖这些注解来进行相关操作，而是更多地依据全局配置（如上述其他配置
     *          项）来控制序列化和反序列化过程，适用于希望统一管理 JSON 处理规则，减少因注解使用不当或
     *          过多导致的复杂性的场景
     * 7. addModule(new JavaTimeModule())
     *      这是序列化LocalDateTIme和LocalDate的必要配置,由Jackson-data-JSR310实现
     *  <br>
     * 8. .defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT))
     *      所有的日期格式都统一为以下的样式，即yyyy-MM-dd HH:mm:ss
     * <br>
     * 9. .addModule(new SimpleModule().addSerializer(LocalDateTime.class, new
     *      LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) //序列时起作用
     *      .addDeserializer(LocalDateTime.class, new
     *      LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) //反序列时起作用)
     *      对于LocalDateTIme和LocalDate时间类型序列列化时转换为json字符串时可以按照指定格式转换，
     *      反序列化时可以也可以将指定的时间字符串类型转换为LocalDateTIme和LocalDate。
     */
    static {
        OBJECT_MAPPER = JsonMapper.builder().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
                .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
                .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)
                //不适用默认的dateTime进行序列化,使用JSR310的LocalDateTimeSerializer
                .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false)
                .configure(MapperFeature.USE_ANNOTATIONS, false)
                //重点,这是序列化LocalDateTIme和LocalDate的必要配置,由Jackson-data-JSR310实现
                .addModule(new JavaTimeModule())
                .addModule(new SimpleModule()
                        .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(CommonConstants.STANDARD_FORMAT)))
                        .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(CommonConstants.STANDARD_FORMAT)))
                )
                //所有的日期格式都统一为以下的样式，即yyyy-MM-dd HH:mm:ss
                .defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT))
                // 只针对非空的值进行序列化
                .serializationInclusion(JsonInclude.Include.NON_NULL)
                .build();
    }

    /**
     * 禁用构造
     */
    private JsonUtil() {
    }


    /**
     * 对象转Json格式字符串
     * @param obj 对象
     * @return Json格式字符串
     * @param <T> 对象类型
     */
    public static <T> String obj2String(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj :
                    OBJECT_MAPPER.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.warn("Parse Object to String error : {}", e.getMessage());
            return null;
        }
    }


    /**
     * 对象转Json格式字符串(格式化的Json字符串)
     * @param obj 对象
     * @return 美化的Json格式字符串
     * @param <T> 对象类型
     */
    public static <T> String obj2StringPretty(T obj) {
        if (obj == null) {
            return null;
        }
        try {
            return obj instanceof String ? (String) obj :
                    OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.warn("Parse Object to String error : {}", e.getMessage());
            return null;
        }
    }


    /**
     * 字符串转换为自定义对象
     * @param str 要转换的字符串
     * @param clazz 自定义对象的class对象
     * @return 自定义对象
     * @param <T> 对象类型
     */
    public static <T> T string2Obj(String str, Class<T> clazz) {
        if (str == null || str.isEmpty()|| clazz == null) {
            return null;
        }
        try {
            return clazz.equals(String.class) ? (T) str :
                    OBJECT_MAPPER.readValue(str, clazz);
        } catch (Exception e) {
            log.warn("Parse String to Object error : {}", e.getMessage());
            return null;
        }
    }



    /**
     * 字符串转换为自定义对象,支持复杂的泛型嵌套
     *
     * @param str json字符串
     * @param valueTypeRef 对象模板信息
     *        使用如 new TypeReference<List<Map<String,TestObj>>>() {}
     * @return 对象类对应的对象
     * @param <T> 对象类
     */
    public static <T> T string2Obj(String str, TypeReference<T> valueTypeRef) {
        if (StringUtils.isEmpty(str) || valueTypeRef == null) {
            return null;
        }
        try {
            return OBJECT_MAPPER.readValue(str,valueTypeRef);
        } catch (Exception e) {
            log.warn("Parse String to Object error : {}", e.getMessage());
            return null;
        }
    }

    /**
     * 字符串转换为自定义字段转为list,支持List嵌套简单对象
     * @param str json字符串
     * @param clazz 对象类
     * @return 对象列表
     * @param <T> 对象类型
     */
    public static <T> List<T> string2List(String str, Class<T> clazz) {
        if (str == null || str.isEmpty()|| clazz == null) {
            return null;
        }
        JavaType javaType = OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, clazz);
        try {
            return OBJECT_MAPPER.readValue(str, javaType);
        }catch (IOException e) {
            log.warn("Parse String to List error : {}", e.getMessage());
            return null;
        }
    }

    /**
     * 字符串转换为自定义字段转为map,支持Map嵌套简单对象
     * @param str str 字符串信息
     * @param valueClass valueClass value的类别
     * @return map对象
     * @param <T> value 的类型
     */
    public static <T> Map<String, T> string2Map(String str, Class<T>
            valueClass){
        if (str == null || str.isEmpty() || valueClass == null) {
            return null;
        }
        JavaType javaType =
                OBJECT_MAPPER.getTypeFactory().constructMapType(LinkedHashMap.class,
                        String.class, valueClass);
        try {
            return OBJECT_MAPPER.readValue(str, javaType);
        } catch (JsonProcessingException e) {
            log.warn("Parse String to Map error : {}", e.getMessage());
            return null;
        }
    }
}
